package com.dldata.devtools30.repository.common;

import com.dldata.devtools30.businessmodel.QueryResult;
import com.dldata.devtools30.utils.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.*;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;


/**
 * Created by zhangyy on 2017-04-12.
 */
@Transactional
@Repository("cmmonApiDao")
public class CommonApiDao {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    protected EntityManager entityManager;

    @Autowired
    protected LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void clear(){
        entityManager.clear();
    }
    
    public <T> void deleteObj(Object entityObj) {
        entityManager.remove(entityObj);;
    }
    
    /**
     *
     * @param entityList
     */
    @Transactional
    public void batchDelete(Collection entityList){
        for (Iterator iterator = entityList.iterator(); iterator.hasNext(); ) {
            Object next = iterator.next();
            entityManager.remove(next);
        }
    }

    public <T> void delete(Class<T> entityClass,Object entityid) {
        delete(entityClass, new Object[]{entityid});
    }

    public <T> void delete(Class<T> entityClass,Object[] entityids) {
        for(Object id : entityids){
            entityManager.remove(entityManager.getReference(entityClass, id));
        }
    }

    public <T> void delete(Class<T> entityClass,Object[] entityids,boolean isflush) {
        for(Object id : entityids){
            entityManager.remove(entityManager.getReference(entityClass, id));
        }
        if(isflush){
            entityManager.flush();
        }
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> T find(Class<T> entityClass, Object entityId) {
        return entityManager.find(entityClass, entityId);
    }

    public <T> T save(T entity) {
        JpaEntityInformation emtityInfoMation=null;
        try {
            Method[] methods=
            JpaEntityInformationSupport.class.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if(!method.getName().equals("getEntityInformation")){
                    continue;
                }
                Class<?>[] classes=method.getParameterTypes();
                if(classes.length==2
                        &&classes[0].isAssignableFrom(Class.class)
                        && classes[1].isAssignableFrom(EntityManager.class)){
                    emtityInfoMation=(JpaEntityInformation)method.invoke(null,entity.getClass(), entityManager);
                    break;
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        if(emtityInfoMation==null){
            emtityInfoMation= JpaEntityInformationSupport.getEntityInformation(entity.getClass(), entityManager);
        }
        if(emtityInfoMation.isNew(entity)){
            entityManager.persist(entity);
        }else{
            entityManager.merge(entity);
        }
        return entity;
    }

    public <T> T save(T entity,boolean isflush) {
        JpaEntityInformation emtityInfoMation=null;
        try {
            Method[] methods=
                    JpaEntityInformationSupport.class.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if(!method.getName().equals("getEntityInformation")){
                    continue;
                }
                Class<?>[] classes=method.getParameterTypes();
                if(classes.length==2
                        &&classes[0].isAssignableFrom(Class.class)
                        && classes[1].isAssignableFrom(EntityManager.class)){
                    emtityInfoMation=(JpaEntityInformation)method.invoke(null,entity.getClass(), entityManager);
                    break;
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        if(emtityInfoMation==null){
            emtityInfoMation= JpaEntityInformationSupport.getEntityInformation(entity.getClass(), entityManager);
        }
        if(emtityInfoMation.isNew(entity)){
            entityManager.persist(entity);
        }else{
            entityManager.merge(entity);
        }
        if(isflush){
            entityManager.flush();
        }
        return entity;
    }



    /**
     * 先分出更新和添加,再批量
     * @param entityList
     * @param <T>
     * @return
     */
    public <T> Collection<T> batchSave(Collection<T> entityList) {
        JpaEntityInformation emtityInfoMation=null;
        Method method1 =null;
        try {
            Method[] methods=
                    JpaEntityInformationSupport.class.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if(!method.getName().equals("getEntityInformation")){
                    continue;
                }
                Class<?>[] classes=method.getParameterTypes();
                if(classes.length==2
                        &&classes[0].isAssignableFrom(Class.class)
                        && classes[1].isAssignableFrom(EntityManager.class)){
                    method1=method;
                    break;
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        List<T> insertList=new LinkedList<>();
        List<T> updateList=new LinkedList<>();
        for (Iterator<T> iterator = entityList.iterator(); iterator.hasNext(); ) {
            T entity = iterator.next();
            if(method1!=null&&emtityInfoMation==null){
                try {
                    emtityInfoMation=(JpaEntityInformation)method1.invoke(null,entity.getClass(), entityManager);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(emtityInfoMation==null){
                emtityInfoMation=JpaEntityInformationSupport.getEntityInformation(entity.getClass(), entityManager);
            }
            if(emtityInfoMation.isNew(entity)){
                insertList.add(entity);
            }else{
                updateList.add(entity);
            }
        }
        batchInsert(insertList);
        batchUpdate(updateList);
        return entityList;
    }

    public <T> Collection<T> batchUpdate(Collection<T> entityList) {
//        BatchingBatch
        Object batch_sizeObj= localContainerEntityManagerFactoryBean.getJpaPropertyMap().get("hibernate.jdbc.batch_size");
        int batch_size=1000;
        if(!StringUtil.isEmptyOrLength0(batch_sizeObj)&& StringUtils.isNumeric(batch_sizeObj.toString())){
            batch_size=Integer.parseInt(batch_sizeObj.toString());
        }
        try{
            int i = 0;
            for (Iterator<T> iterator = entityList.iterator(); iterator.hasNext(); ) {
                T entity = iterator.next();
                entityManager.merge(entity);
                i++;
                if ( (i) % batch_size == 0 ) {//20, same as the JDBC batch size
                    entityManager.flush();
                }
            }
            entityManager.flush();
        }catch (Exception ex){
            logger.error(ex.getMessage(),ex);
            throw ex;
        }finally {
            if(entityManager!=null){
                entityManager.flush();
            }
        }
        return entityList;
    }



    public <T> Collection<T> batchInsert(Collection<T> entityList) {
//        BatchingBatch
        Object batch_sizeObj= localContainerEntityManagerFactoryBean.getJpaPropertyMap().get("hibernate.jdbc.batch_size");
        int batch_size=1000;
        if(!StringUtil.isEmptyOrLength0(batch_sizeObj)&& StringUtils.isNumeric(batch_sizeObj.toString())){
            batch_size=Integer.parseInt(batch_sizeObj.toString());
        }
        try{
            int i = 0;
            for (Iterator<T> iterator = entityList.iterator(); iterator.hasNext(); ) {
                T entity = iterator.next();
                entityManager.persist(entity);
                i++;
                if ( (i) % batch_size == 0 ) {//20, same as the JDBC batch size
                    entityManager.flush();
                }
            }
            entityManager.flush();
        }catch (Exception ex){
            logger.error(ex.getMessage(),ex);
            throw ex;
        }finally {
            if(entityManager!=null){
                entityManager.flush();
            }
        }
        return entityList;
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> long getCount(Class<T> entityClass) {
        return (Long) entityManager.createQuery("select count("+ getCountField(entityClass) +") from "+ getEntityName(entityClass)+ " o").getSingleResult();
    }

    public void update(Object entity) {
        entityManager.merge(entity);
    }

    public void update(Object entity,boolean isflush) {
        entityManager.merge(entity);
        if(isflush){
            entityManager.flush();
        }
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass,
                                            int firstindex, int maxresult, LinkedHashMap<String, String> orderby) {
        return getScrollData(entityClass,firstindex,maxresult,null,new HashMap<>(),orderby);
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass,
                                            int firstindex, int maxresult, String wherejpql, Object[] queryParams) {
        return getScrollData(entityClass,firstindex,maxresult,wherejpql,queryParams,null);
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult) {
        return getScrollData(entityClass,firstindex,maxresult,null,new HashMap<>(),null);
    }

    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass) {
        return getScrollData(entityClass, -1, -1);
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult
            , String wherejpql, Object[] queryParams,LinkedHashMap<String, String> orderby) {
        QueryResult qr = new QueryResult<T>();
        String entityname = getEntityName(entityClass);
        Query query = entityManager.createQuery("select o from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql)+ buildOrderby(orderby));
        setQueryParams(query, queryParams);
        if(firstindex!=-1 && maxresult!=-1) query.setFirstResult(firstindex).setMaxResults(maxresult);
        qr.setResultlist(query.getResultList());
        query = entityManager.createQuery("select count("+ getCountField(entityClass)+ ") from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql));
        setQueryParams(query, queryParams);
        qr.setTotalrecord((Long)query.getSingleResult());
        return qr;
    }

    protected void setQueryParams(Query query, Object[] queryParams){
        if(queryParams!=null && queryParams.length>0){
            for(int i=0; i<queryParams.length; i++){
                query.setParameter(i, queryParams[i]);
            }
        }
    }
    /**
     * 组装order by语句
     * @param orderby
     * @return
     */
    protected String buildOrderby(LinkedHashMap<String, String> orderby){
        StringBuffer orderbyql = new StringBuffer("");
        if(orderby!=null && orderby.size()>0){
            orderbyql.append(" order by ");
            for(String key : orderby.keySet()){
                orderbyql.append("o.").append(key).append(" ").append(orderby.get(key)).append(",");
            }
            orderbyql.deleteCharAt(orderbyql.length()-1);
        }
        return orderbyql.toString();
    }

    /**
     * 获取实体的名称
     * @param <T>
     * @param entityClass 实体类
     * @return
     */
    protected <T> String getEntityName(Class<T> entityClass){
        String entityname = entityClass.getSimpleName();
        Entity entity = entityClass.getAnnotation(Entity.class);
        if(entity.name()!=null && !"".equals(entity.name())){
            entityname = entity.name();
        }
        return entityname;
    }

    protected <T> String getCountField(Class<T> clazz){
        String out = "o";
        try {
            PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
            for(PropertyDescriptor propertydesc : propertyDescriptors){
                Method method = propertydesc.getReadMethod();
                if(method!=null && method.isAnnotationPresent(EmbeddedId.class)){
                    PropertyDescriptor[] ps = Introspector.getBeanInfo(propertydesc.getPropertyType()).getPropertyDescriptors();
                    out = "o."+ propertydesc.getName()+ "." + (!ps[1].getName().equals("class")? ps[1].getName(): ps[0].getName());
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return out;
    }


    @Transactional(readOnly=true,propagation= Propagation.NOT_SUPPORTED)
    public <T> T findone(Class<T> entityClass, Object entityId) {
        return entityManager.find(entityClass, entityId);
    }

    public void flush(){
        try{
            entityManager.flush();
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }


    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass
            , String wherejpql,Map<String, Object> queryParams,LinkedHashMap<String, String> orderby) {
        return getScrollData(entityClass,0,Integer.MAX_VALUE,wherejpql,queryParams,orderby);
    }


    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass
            , String wherejpql,Object[] queryParams,LinkedHashMap<String, String> orderby) {
        return getScrollData(entityClass,0,Integer.MAX_VALUE,wherejpql,queryParams,orderby);
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass
            , String wherejpql,Object[] queryParams) {
        return getScrollData(entityClass,0,Integer.MAX_VALUE,wherejpql,queryParams,new LinkedHashMap<>());
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult,String selectsql,String totalselectsql
            , String wherejpql,Map<String, Object> queryParams,LinkedHashMap<String, String> orderby) {
        QueryResult qr = new QueryResult<T>();
        String entityname = getEntityName(entityClass);
        String selectsqlDefault = "select o from " + entityname + " o ";
        selectsql=StringUtil.isEmptyOrLength0(selectsql)?selectsqlDefault:selectsql;
        Query query = entityManager.createQuery(selectsql + (wherejpql == null ? "" : "where " + wherejpql) + buildOrderby(orderby));
        setQueryParams(query, queryParams);
        if(firstindex!=-1 && maxresult!=-1) query.setFirstResult(firstindex).setMaxResults(maxresult);
        qr.setResultlist(query.getResultList());
        String totalselectsqlDefault = "select count(" + getCountField(entityClass) + ") from " + entityname + " o ";
        totalselectsql=StringUtil.isEmptyOrLength0(totalselectsql)?totalselectsqlDefault:totalselectsql;
        query = entityManager.createQuery(totalselectsql + (wherejpql == null ? "" : "where " + wherejpql));
        setQueryParams(query, queryParams);
        qr.setTotalrecord((Long)query.getSingleResult());
        return qr;
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
    public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult
            , String wherejpql,Map<String, Object> queryParams,LinkedHashMap<String, String> orderby) {
        QueryResult qr = new QueryResult<T>();
        String entityname = getEntityName(entityClass);
        Query query = entityManager.createQuery("select o from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql)+ buildOrderby(orderby));
        setQueryParams(query, queryParams);
        if(firstindex!=-1 && maxresult!=-1) query.setFirstResult(firstindex).setMaxResults(maxresult);
        qr.setResultlist(query.getResultList());
        query = entityManager.createQuery("select count("+ getCountField(entityClass)+ ") from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql));
        setQueryParams(query, queryParams);
        qr.setTotalrecord((Long)query.getSingleResult());
        return qr;
    }

    protected void setQueryParams(Query query, Map<String, Object> queryParams){
        if(queryParams!=null){
            for (Map.Entry<String, Object> queryParamsEntry :
                    queryParams.entrySet()) {
                query.setParameter(queryParamsEntry.getKey(), queryParamsEntry.getValue());
            }
        }
    }
}